/*
 * Copyright © 2018 www.noark.xyz All Rights Reserved.
 *
 * 感谢您选择Noark框架，希望我们的努力能为您提供一个简单、易用、稳定的服务器端框架 ！
 * 除非符合Noark许可协议，否则不得使用该文件，您可以下载许可协议文件：
 *
 *        http://www.noark.xyz/LICENSE
 *
 * 1.未经许可，任何公司及个人不得以任何方式或理由对本框架进行修改、使用和传播;
 * 2.禁止在本项目或任何子项目的基础上发展任何派生版本、修改版本或第三方版本;
 * 3.无论你对源代码做出任何修改和改进，版权都归Noark研发团队所有，我们保留所有权利;
 * 4.凡侵犯Noark版权等知识产权的，必依法追究其法律责任，特此郑重法律声明！
 */
package xyz.noark.orm.accessor.mongo;

import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.IndexOptions;
import com.mongodb.client.result.DeleteResult;
import org.bson.Document;
import org.bson.conversions.Bson;
import xyz.noark.core.exception.DataException;
import xyz.noark.orm.EntityMapping;
import xyz.noark.orm.FieldMapping;
import xyz.noark.orm.accessor.AbstractDataAccessor;
import xyz.noark.orm.accessor.adaptor.ValueAdaptorManager;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.StreamSupport;

import static com.mongodb.client.model.Filters.eq;
import static com.mongodb.client.model.Filters.in;
import static xyz.noark.log.LogHelper.logger;

/**
 * MongoDB数据访问器
 *
 * @author 小流氓[176543888@qq.com]
 * @since 4.0
 */
public class MongoDataAccessor extends AbstractDataAccessor {
    private final MongoClient mongoClient;
    private final MongoDatabase database;

    public MongoDataAccessor(String mongoUrl, String databaseName) {
        this(MongoClients.create(mongoUrl), databaseName);
    }

    public MongoDataAccessor(MongoClient mongoClient, String databaseName) {
        this.mongoClient = mongoClient;
        this.database = mongoClient.getDatabase(databaseName);
        this.initDataAccessorAfter();
    }

    /**
     * 初始化之后的逻辑
     */
    protected void initDataAccessorAfter() {
        Document buildInfo = mongoClient.getDatabase("admin").runCommand(new Document("buildInfo", 1));
        String ver = buildInfo.getString("version");
        logger.info("database product name: MongoDB, version:{}", ver);
    }

    @Override
    public <T> void checkupEntityFieldsWithDatabase(EntityMapping<T> em) {
        super.checkupEntityFieldsWithDatabase(em);
        // 如果有玩家ID，那要给他补个查询索引, _id字段为主键，默认就是唯一索引，不用再画足
        if (em.getPlayerId() != null) {
            MongoCollection<Document> collection = database.getCollection(em.getTableName());
            if (!hasPlayerIdIndex(collection)) {
                // 升序索引
                IndexOptions options = new IndexOptions().background(true).name("player_id_asc_idx");
                collection.createIndex(new Document("PlayerId", 1), options);
                logger.warn("集合[{}]为字段[{}]自动创建升序索引 name=player_id_asc_idx"
                        , em.getTableName(), em.getPlayerId().getColumnName());
            }
        }
    }

    public static boolean hasPlayerIdIndex(MongoCollection<Document> collection) {
        return StreamSupport.stream(collection.listIndexes().spliterator(), false)
                .anyMatch(doc -> {
                    Object key = doc.get("key", Document.class);
                    return key != null && key.toString().contains("PlayerId");
                });
    }

    private Document buildDocument(EntityMapping<?> em, Object entity) {
        try {
            Document document = new Document();
            for (FieldMapping fm : em.getFieldMapping()) {
                ValueAdaptorManager.getValueAdaptor(fm.getType()).persistFieldToStorage(em, entity, fm, document);
            }
            return document;
        } catch (Exception e) {
            throw new DataException("存储数据时异常，请查看实体类[" + em.getEntityClass().getName() + "]配置", e);
        }
    }

    public <T> T newEntity(final EntityMapping<T> em, Document document) {
        try {
            T result = em.newEntity();
            for (FieldMapping fm : em.getFieldInfo()) {
                ValueAdaptorManager.getValueAdaptor(fm.getType()).retrieveFieldFromStorage(document, fm, em, result);
            }
            return result;
        } catch (Exception e) {
            throw new DataException("载入数据时异常，请查看实体类[" + em.getEntityClass().getName() + "]配置", e);
        }
    }

    @Override
    public <T> int insert(EntityMapping<T> em, T entity) {
        MongoCollection<Document> collection = database.getCollection(em.getTableName());
        collection.insertOne(this.buildDocument(em, entity));
        return 1;
    }

    @Override
    public <T> int[] batchInsert(EntityMapping<T> em, List<T> entityList) {
        List<Document> documentList = new ArrayList<>(entityList.size());
        for (T entity : entityList) {
            documentList.add(this.buildDocument(em, entity));
        }
        MongoCollection<Document> collection = database.getCollection(em.getTableName());
        collection.insertMany(documentList);
        return new int[0];
    }


    @Override
    public <T> int delete(EntityMapping<T> em, T entity) {
        MongoCollection<Document> collection = database.getCollection(em.getTableName());
        Bson filter = eq("_id", em.getPrimaryIdValue(entity));
        DeleteResult deleteResult = collection.deleteOne(filter);
        return (int) deleteResult.getDeletedCount();
    }

    @Override
    public <T> int[] batchDelete(EntityMapping<T> em, List<T> entityList) {
        List<Serializable> idList = new ArrayList<>(entityList.size());
        for (T entity : entityList) {
            idList.add(em.getPrimaryIdValue(entity));
        }
        MongoCollection<Document> collection = database.getCollection(em.getTableName());
        collection.deleteMany(in("_id", idList));
        return new int[0];
    }

    @Override
    public <T> int update(EntityMapping<T> em, T entity) {
        MongoCollection<Document> collection = database.getCollection(em.getTableName());
        Bson filter = eq("_id", em.getPrimaryIdValue(entity));
        collection.replaceOne(filter, this.buildDocument(em, entity));
        return 1;
    }

    @Override
    public <T> int[] batchUpdate(EntityMapping<T> em, List<T> entityList) {
        MongoCollection<Document> collection = database.getCollection(em.getTableName());
        for (T entity : entityList) {
            Bson filter = eq("_id", em.getPrimaryIdValue(entity));
            collection.replaceOne(filter, this.buildDocument(em, entity));
        }
        return new int[0];
    }

    @Override
    public <T, K extends Serializable> T load(EntityMapping<T> em, K id) {
        MongoCollection<Document> collection = database.getCollection(em.getTableName());
        Document document = collection.find(eq("_id", id)).first();
        if (document == null) {
            return null;
        }
        return this.newEntity(em, document);
    }

    @Override
    public <T> List<T> loadAll(EntityMapping<T> em) {
        MongoCollection<Document> collection = database.getCollection(em.getTableName());
        List<T> result = new LinkedList<>();
        collection.find().forEach(document -> {
            result.add(this.newEntity(em, document));
        });
        return result;
    }

    @Override
    public <T> List<T> loadAll(EntityMapping<T> em, Serializable playerId) {
        MongoCollection<Document> collection = database.getCollection(em.getTableName());
        Bson filter = eq(em.getPlayerId().getColumnName(), playerId);
        List<T> result = new LinkedList<>();
        collection.find(filter).forEach(document -> {
            result.add(this.newEntity(em, document));
        });
        return result;
    }
}
